﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Perovich.GameObjects.Interfaces;
using System.Collections;
using Microsoft.Xna.Framework;
using Perovich.GameObjects.Components;

namespace Perovich.GameObjects.Entities
{
    public class EntityComponentCollection : DrawableComponent, IEntityComponentCollection
    {
        Dictionary<Type, IEntityComponent> components = new Dictionary<Type, IEntityComponent>();

        bool isReadOnly;

        public EntityComponentCollection(ICanSupportComponents parent)
            :base(parent)
        {
        }

        #region IEntityComponentCollection Members

        public event EventHandler<EntityComponentEventArgs> ComponentAdded;

        protected virtual void OnComponentAdded(EntityComponentEventArgs e)
        {
            if (ComponentAdded != null)
            {
                ComponentAdded(this, e);
            }
        }

        public event EventHandler<EntityComponentRemovedEventArgs> ComponentRemoved;

        protected virtual void OnComponentRemoved(EntityComponentRemovedEventArgs e)
        {
            if (ComponentRemoved != null)
            {
                ComponentRemoved(this, e);
            }

            // looping through to avoid removing a dependency (it'll just get added again)
            IEntityComponent[] tmpComponents = new IEntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++)
            {
                tmpComponents[i].OnAttached(e.UpdateDependencies);
            }
        }

        public void Add(IEntityComponent item, bool overwrite)
        {
            if (IsReadOnly)
            {
                throw new ArgumentException("The entity is read-only.");
            }
            /*
            // hack: to avoid using Entity.Add(new Component()), but this proves troublesome as you're allowed to pass an array of components for entity creation
            if (item.Owner == null) {
                throw new ArgumentException("Cannot add an owner-less component.");
            }
            */
            if (components.ContainsKey(item.GetType()))
            {
                if (overwrite)
                {
                    // remove the existing component, and remove it immediately to be able to execute the following
                    // without troubles
                    RemoveImmediate(components[item.GetType()], true);
                }
                else
                {
                    return;
                }
            }

            // if we just removed the component, but it's still here it means another component  
            // was dependant on it and it had to be attached again
            if (!components.ContainsKey(item.GetType()))
            {
                components.Add(item.GetType(), item);

                // fire this event now, in order to be able to assign an Owner before initializing
                // note: the factory will actually do this for us, but we can't use it in all situations so this is a hacky solution
                OnComponentAdded(new EntityComponentEventArgs(item));

                item.OnAttached(true);

            }
        }

        public T Find<T>() where T : IEntityComponent
        {
            Type type = typeof(T);

            if (components.ContainsKey(type))
            {
                return (T)components[type];
            }

            // todo: exception
            //throw new Exception("Well, the component wasn't found.");
            return default(T);
        }

        public IEnumerable<T> FindAll<T>() where T : IEntityComponent
        {

            // to avoid garbage, we create an array we can 'for' over
            IEntityComponent[] tmpComponents = new IEntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++)
            {
                if (tmpComponents[i] is T)
                {
                    yield return (T)tmpComponents[i];
                }
            }
        }

        public bool Contains<T>() where T : IEntityComponent
        {
            return components.ContainsKey(typeof(T));
        }

        List<IEntityComponent> componentsPendingRemoval = new List<IEntityComponent>();
        Dictionary<IEntityComponent, bool> updateDependencies = new Dictionary<IEntityComponent, bool>(); // component:update dependencies?

        public override void Update(GameTime gameTime)
        {
            if (componentsPendingRemoval.Count > 0)
            {
                for (int i = 0; i < componentsPendingRemoval.Count; i++)
                {
                    if (componentsPendingRemoval[i].Owner.Contains(componentsPendingRemoval[i]))
                    {
                        // it will only be contained if it was not placed in the list due to a Remove call 
                        // (ie. RemoveImmediate would have removed it prior to queuing it up)
                        //componentsPendingRemoval[i].Owner.RemoveImmediate(componentsPendingRemoval[i], true);

                        // experimental:
                        // update: seems to work flawlessly
                        bool updateDeps = true;
                        if (updateDependencies.ContainsKey(componentsPendingRemoval[i]))
                        {
                            updateDeps = updateDependencies[componentsPendingRemoval[i]];
                        }

                        componentsPendingRemoval[i].Owner.RemoveImmediate(componentsPendingRemoval[i], updateDeps);

                        if (updateDependencies.ContainsKey(componentsPendingRemoval[i]))
                        {
                            updateDependencies.Remove(componentsPendingRemoval[i]);
                        }
                    }

                    componentsPendingRemoval[i].Dispose();
                    componentsPendingRemoval.RemoveAt(i);
                }
            }
            base.Update(gameTime);
        }

        public bool Remove<T>() where T : IEntityComponent
        {
            if (components.ContainsKey(typeof(T)))
            {
                IEntityComponent component = components[typeof(T)];
                // queue up component for removal
                if (!componentsPendingRemoval.Contains(component))
                {
                    componentsPendingRemoval.Add(component);

                    return true;
                }

                return false;
            }

            return false;
        }

        public bool RemoveImmediate<T>(bool updateDependencies) where T : IEntityComponent
        {
            Type type = typeof(T);

            if (components.ContainsKey(type))
            {
                return RemoveImmediate(components[type], updateDependencies);
            }

            return false;
        }

        public bool RemoveImmediate(IEntityComponent component, bool updateDependencies)
        {
            Type t = component.GetType();

            if (components.ContainsKey(t))
            {
                // dispose
                component.Dispose();
                // remove it immediately
                bool result = components.Remove(t);
                // but queue it up to be removed from maintained lists aswell
                //component.Owner.Manager.Remove(component);

                OnComponentRemoved(new EntityComponentRemovedEventArgs(component, updateDependencies));

                return result;
            }

            return false;
        }

        public IEntityComponent this[Type key]
        {
            get
            {
                return components[key];
            }
        }

        #endregion

        #region ICollection<IEntityComponent> Members

        public void Add(IEntityComponent item)
        {
            Add(item, false);
        }

        public void Clear()
        {
            // this can be troublesome, consider:
            // if we remove a component that another component is dependant on, it'll just get added again
            // this means we could miss components.. 
            // solutions:
            // hack: solution could be recursively calling clear until there's no components left :D
            // solved: removals now specify whether or not to update dependencies (every component will still
            // be initialized, but will only check/add dependencies if told)
            IEntityComponent[] tmpComponents = new IEntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++)
            {
                RemoveImmediate(tmpComponents[i], false);
            }
        }

        public bool Contains(IEntityComponent item)
        {
            return components.ContainsKey(item.GetType());
        }

        public void CopyTo(IEntityComponent[] array, int arrayIndex)
        {
            components.Values.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get
            {
                return components.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return isReadOnly;
            }
            set
            {
                if (isReadOnly != value)
                {
                    isReadOnly = value;
                }
            }
        }

        public bool Remove(IEntityComponent item)
        {
            if (components.ContainsKey(item.GetType()))
            {
                // queue up component for removal
                if (!componentsPendingRemoval.Contains(item))
                {
                    componentsPendingRemoval.Add(item);

                    return true;
                }

                return false;
            }

            return false;
        }

        #endregion

        #region IEnumerable<IEntityComponent> Members

        public IEnumerator<IEntityComponent> GetEnumerator()
        {
            return components.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return components.GetEnumerator();
        }

        #endregion
    }
}
